#pragma once
#include <iostream>
#include <unistd.h>
#include <queue>
#include <pthread.h>
using namespace std;
#include "Task.hpp"
#define NUM 5

template<class T>
class ThreadPool 
{
  private:
    queue<T> _task_queue;
    int _thread_num;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

  private:
    bool IsEmpty()
    {
      return _task_queue.size() == 0;
    }

    bool IsFull()
    {
      return _task_queue.size() == _thread_num;
    }

    void LockQueue()
    {
      pthread_mutex_lock(&_mutex);
    }

    void UnlockQueue()
    {
      pthread_mutex_unlock(&_mutex);
    }

    void Wait()
    {
      pthread_cond_wait(&_cond , &_mutex);
    }

    void WakeUp()
    {
      pthread_cond_signal(&_cond);
    }

  public:
    ThreadPool(int num = NUM)
      :_thread_num(num)
    {
      pthread_mutex_init(&_mutex , NULL);
      pthread_cond_init(&_cond , NULL);
    }
    ~ThreadPool()
    {
      pthread_mutex_destroy(&_mutex);
      pthread_cond_destroy(&_cond);
    }

  public:
    static void* Routine(void* arg)
    {
      pthread_detach(pthread_self());
      ThreadPool* self = (ThreadPool*)arg;

      while(true)
      {
        self->LockQueue();
        while(self->IsEmpty())
        {
          self->Wait();
        }

        T task;
        self->Pop(task);
        self->UnlockQueue();

        task.run();
      }
      
    }

    void ThreadPoolInit()
    {
      pthread_t tid;
      for(int i = 0; i < _thread_num ; i++)
      {
        pthread_create(&tid , NULL , Routine , this);
      }
    }

    void Push(const T& task)
    {
      LockQueue();
      _task_queue.push(task);
      UnlockQueue();
      WakeUp();
    }

    void Pop(T& task)
    {
      task = _task_queue.front();
      _task_queue.pop();
    }
};
